上一篇在實作 merchant service 時,有人應該注意到已經把新增、更新商家的方法寫出來了,現在就是要用 Angular 的內建表單把它們串起來,利用表單取得使用者提供的資料,之後更新商家清單的資訊。
主要是透過 Template (component 的 html 檔) 中, element 加上 Directive 的方式去建立或更新 data ,較常用在比較簡單的表單,但彈性較低,本次範例就是以此表單來演練
在 component 的 ts 檔實體化 form 的 model,相對來說更具擴展性、重用性與可預測性。
詳細的介紹可在參考官方文件
這裡使用 Material 的 Dialog ,所以需要在 merchant-list.component.ts 注入 MatDialog, 並修改 openEditModal 的方法,他負責開啟 Dialog,在這次的範例裡他可能是新增商家資訊或編輯商家資訊。在第 23、29 行是開啟 Dialog 的方法,open 方法第 1 個參數是要參考的 Dialog component、第 2 個參數是給 Dialog 的組態物件。
第 35 行之後,是當 Dialog 關閉時會傳出型別為 Merchant 物件,要傳出什麼樣的 data 將會在 Dialog component 中設定。之後再依照開啟 Dailog 時的模式(新增或編輯),呼叫之前已經實作的 createMerchant 或 updateMerchant 方法。
如果版本在 Angular 9 之前,需要再 module 中把 Dialog component 加到 entryComponents 陣列中。參考這裡
// ...省略
export class MerchantListComponent implements OnInit {
merchants$: Observable<Merchant[]>;
constructor(
private merchantsService: MerchantsService,
public dialog: MatDialog
) {}
ngOnInit(): void {
this.merchants$ = this.merchantsService.getMerchants();
}
openEditModal(mode, merchantId?): void {
let dialogRef;
const modalComponent = MerchantEditComponent;
const modalWith = '500px';
if (merchantId) {
this.merchantsService
.getMerchantById(merchantId)
.pipe(take(1))
.subscribe((merchant) => {
dialogRef = this.dialog.open(modalComponent, {
width: modalWith,
data: { mode, merchant },
});
});
} else {
dialogRef = this.dialog.open(modalComponent, {
width: modalWith,
data: { mode },
});
}
dialogRef.afterClosed().subscribe((merchant) => {
if (!merchant) return;
switch (mode) {
case 'create':
this.createMerchant(merchant);
break;
case 'edit':
this.updateMerchant(merchant);
break;
}
});
}
// ...省略
}
在前面已經有建立 merchant-edit.component 了,我們現在將要做一些調整,目標如下
在 class 裡,需要 merchant 屬性(第 3 行),這將會是 Template-driven forms 要綁定的 model。然後注入 MAT_DIALOG_DATA
,從此物件中取得傳來的 merchant(第 7 行),這裡要注意的是傳來的 merchant 是 by reference 的 object ,所以這裡需要做做深拷貝(第 13 行),避免在更動表單資訊時,同時改到外部 componet 的狀態。
// ...省略
export class MerchantEditComponent implements OnInit {
merchant = new Merchant();
constructor(
public dialogRef: MatDialogRef<MerchantEditComponent>,
@Inject(MAT_DIALOG_DATA) public data,
private clonerSevice: ClonerService
) {}
ngOnInit(): void {
if (this.data.merchant) {
this.merchant = this.clonerSevice.deepClone(this.data.merchant);
}
}
onNoClick(): void {
this.dialogRef.close();
}
}
Template-driven forms 的重點在於 element 的 Directives,我們把表單層級(ngForm)的模板指定給名為 merchantForm 的 Template reference variables(第 3 行),以及每個 field 層級 (ngModel) 的模板指定給對應名稱的 Template reference variables(第 12 行),這裡的用途在於可以在 template 中取用該物件,像是在顯示驗證訊息時(第 14 行)。每個 input 需定義 name (第 11 行) 且需要用 [(ngModel)]
綁定對應 model (剛剛在 class 裡的 merchant)(第 12 行)。
最後在關閉 Dailog 時,要利用[mat-dialog-close]
directive 把使用者已經填好的資訊,且通過表單驗證後的資料傳出去(第 77 行)。
<h1 mat-dialog-title>店家資訊</h1>
<div mat-dialog-content>
<form #merchantForm="ngForm">
<div>
<mat-form-field>
<mat-label>店名</mat-label>
<input
matInput
required
[(ngModel)]="merchant.name"
name="name"
#name="ngModel"
/>
<mat-error *ngIf="name.invalid">{{ "此欄位必填" }}</mat-error>
</mat-form-field>
</div>
<div>
<mat-form-field>
<mat-label>地址</mat-label>
<input
matInput
required
[(ngModel)]="merchant.adress"
name="adress"
#adress="ngModel"
/>
<mat-error *ngIf="adress.invalid">{{ "此欄位必填" }}</mat-error>
</mat-form-field>
</div>
<div>
<mat-form-field>
<mat-label>電話</mat-label>
<input
matInput
required
[(ngModel)]="merchant.phone"
name="phone"
#phone="ngModel"
/>
<mat-error *ngIf="phone.invalid">{{ "此欄位必填" }}</mat-error>
</mat-form-field>
</div>
<div>
<mat-form-field>
<mat-label>網站</mat-label>
<input
matInput
required
[(ngModel)]="merchant.website"
name="website"
#website="ngModel"
/>
<mat-error *ngIf="website.invalid">{{ "此欄位必填" }}</mat-error>
</mat-form-field>
</div>
<div>
<mat-form-field>
<mat-label>圖片</mat-label>
<input
matInput
required
[(ngModel)]="merchant.logo"
name="logo"
#logo="ngModel"
/>
<mat-error *ngIf="logo.invalid">{{ "此欄位必填" }}</mat-error>
</mat-form-field>
</div>
</form>
</div>
<div mat-dialog-actions>
<button mat-button (click)="onNoClick()">取消</button>
<button
[disabled]="!merchantForm.valid"
mat-raised-button
color="primary"
[mat-dialog-close]="merchant"
>
儲存
</button>
</div>
因為本篇開發的表單邏輯並不複雜,所以使用 Template-driven forms 方式達到目的,可參考完整程式碼,下一篇將會介紹 Angular 的路由機制。